#include "ppu.h"
#include "nes.h"

#include <stdio.h>
#include <string.h>

CPpu::CPpu(CNes* nes)
{
  memset(this, 0, sizeof(*this));

  m_NES = nes;

  // PPU memory
  PatternTable[0] = PPU_MEM;
  PatternTable[1] = PPU_MEM + 0x1000;

  NameTable[0] = PPU_MEM + 0x2000;
  NameTable[1] = PPU_MEM + 0x2400;
  NameTable[2] = PPU_MEM + 0x2800;
  NameTable[3] = PPU_MEM + 0x2C00;

  AttribTable[0] = NameTable[0] + 0x3C0;
  AttribTable[1] = NameTable[1] + 0x3C0;
  AttribTable[2] = NameTable[2] + 0x3C0;
  AttribTable[3] = NameTable[3] + 0x3C0;

  BGPalettes = PPU_MEM + 0x3F00;
  SPPalettes = PPU_MEM + 0x3F10;
}

CPpu::~CPpu()
{
}

void
CPpu::Reset()
{
  memset(Register, 0, sizeof(Register));
  m_IsLowAddress = false;
  m_OfsX = m_OfsY = 0;
  m_RamAddress = m_SprRamAddr = 0;
  m_Reg2007Val = 0;
}

void
CPpu::Stop()
{
  memset(PPU_MEM, 0, sizeof(PPU_MEM));
  memset(SprRam,  0, sizeof(SprRam));
  Reset();
}

void
CPpu::FrameStart()
{
}

void
CPpu::VBlankStart()
{
  Register[2] |= PPU_VBLANK_FLAG;
  if (Register[0] & PPU_VBLANK_FLAG) {
    m_NES->NMIReq();
  }
}

void
CPpu::VBlankEnd()
{
  Register[2] &= ~PPU_VBLANK_FLAG;
  Register[2] &= ~PPU_SPRITE_0_HIT;
}

void
CPpu::Scroll(u8 coord)
{
  if (m_IsLowAddress) {
    m_IsLowAddress = false;
    m_OfsY = (int)coord;
    if (m_OfsY >= SCREEN_H) {
      m_OfsY = 0;
    }
  } else {
    m_IsLowAddress = true;
    m_OfsX = (int)coord;
  }
}

void
CPpu::SetAddress(u8 addr)
{
  if (m_IsLowAddress) {
    m_RamAddress |= addr;
    m_IsLowAddress = false;
  } else {
    m_RamAddress = addr & 0x3F;
    m_RamAddress <<= 8;
    m_IsLowAddress = true;
  }
}

void
CPpu::WriteVRam(u8 data)
{
  u16 addr = m_RamAddress & 0x3FFF;
  if (addr < 0x2000) {
    PPU_MEM[addr] = data;
  } else if (addr < 0x3F00) {
    addr <<= 4;
    addr >>= 4;
    int index = addr>>10;
    int ofs   = addr&0x3FF;

    // Both name table and attribute table use this mem-block
    NameTable[index][ofs] = data;
  } else if (addr < 0x4000) {
    data &= (~0x40);
    if (addr & 0x0010) {
      SPPalettes[addr & 0xF] = data;
    } else {
      BGPalettes[addr & 0xF] = data;
    }

    if (!(addr & 0x000F)) {
      BGPalettes[0x00] = SPPalettes[0x00] = data;

      BGPalettes[0x04] = BGPalettes[0x08] =
      BGPalettes[0x0C] = BGPalettes[0x00];

      SPPalettes[0x04] = SPPalettes[0x08] =
      SPPalettes[0x0C] = SPPalettes[0x00];
    }
  }
  // auto inc by r2000.2
  m_RamAddress += (Register[0] & PPU_VRAM_INC_STEP) ? 32 : 1;
}

u8
CPpu::ReadVRam()
{
  u8 value = 0;
  u16 addr = m_RamAddress & 0x3FFF;
  m_RamAddress += (Register[0] & PPU_VRAM_INC_STEP) ? 32 : 1;

  if (addr < 0x2000) {
    value = PPU_MEM[addr];
  } else if (addr < 0x3F00) {
    addr <<= 4;
    addr >>= 4;
    int index = addr>>10;
    int ofs   = addr&0x3FF;
    value = NameTable[index][ofs];
  } else if(addr >= 0x3F00 && addr < 0x4000) {
    if(addr & 0x0010) {
      value = SPPalettes[addr & 0xF];
    } else {
      value = BGPalettes[addr & 0xF];
    }
    // for palette value, it should be directly returned to cpu
    m_Reg2007Val = value;
  }
  u32 old_val = m_Reg2007Val;
  m_Reg2007Val = value;
  return old_val;
}

void
CPpu::SetSprAddr(u8 addr)
{
  m_SprRamAddr = addr;
}

void
CPpu::WriteSprRam(u8 data)
{
  SprRam[m_SprRamAddr++] = data;
}

u8
CPpu::ReadSprRam()
{
  return SprRam[m_SprRamAddr++];
}

void
CPpu::SprDMA(u8* data)
{
  int rotate_len = m_SprRamAddr;
  int normal_len = 0x100-m_SprRamAddr;
  if (rotate_len != 0) {
    memcpy(&SprRam[m_SprRamAddr], data + normal_len, rotate_len);
  }
  memcpy(SprRam, data, normal_len);
}

void
CPpu::SetNameTblBank(bool v)
{
  if (v) {
    NameTable[0] = PPU_MEM + 0x2000;
    NameTable[1] = PPU_MEM + 0x2400;
    NameTable[2] = PPU_MEM + 0x2000;
    NameTable[3] = PPU_MEM + 0x2400;
  } else {
    NameTable[0] = PPU_MEM + 0x2000;
    NameTable[1] = PPU_MEM + 0x2000;
    NameTable[2] = PPU_MEM + 0x2400;
    NameTable[3] = PPU_MEM + 0x2400;
  }
  AttribTable[0] = NameTable[0] + 0x3C0;
  AttribTable[1] = NameTable[1] + 0x3C0;
  AttribTable[2] = NameTable[2] + 0x3C0;
  AttribTable[3] = NameTable[3] + 0x3C0;
}
